/*
 * @Description: 虚拟滚动列表
 * @Author: lcz
 * @LastEditors: lcz
 * @LastEditTime: 2025-07-05 15:54:34
 */
import React, { useState, useRef, useEffect, useCallback, useMemo } from "react";

/**
 * @description: 树结构平铺成一维数组
 * @param {*} node 树节点
 * @param {*} children 子节点字段名
 * @param {*} result 数组
 * @return {*} 一维数组
 */

type flattenTreeType<T> = (node: T, children?: string, result?: T[]) => T[];

export const flattenTree: flattenTreeType<Record<string, any>> = (node, children = "children", result = []) => {
  result.push(node);

  if (node[children] && node[children].length > 0) {
    node[children].forEach((child: Record<string, any>) => flattenTree(child, children, result));
  }

  return result;
};

// 动态高度虚拟滚动配置接口
interface DVirtualListConfig<T> {
  data: T[]; // 数据
  scrollContainer: string; // 滚动容器的选择器
  actualHeightContainer: string; // 总高度元素选择器
  translateContainer: string; // 偏移元素选择器
  itemContainer: string; // 列表项选择器
  itemHeight: number; // 列表项高度
  size: number; // 每次渲染数据量
  openVirtualList?: boolean; // 是否开启虚拟滚动
  defaultVirtual?: boolean; // 默认是否开启滚动
}

// 固定高度虚拟滚动配置接口
interface DVirtualListFixedHeightConfig<T> {
  data: T[]; // 数据
  itemHeight: number; // 列表项高度
  size: number; // 每次渲染数据量
  actualHeightContainer: string; // 总高度元素选择器
  scrollContainer: string; // 滚动容器选择器
  translateContainer: string; // 偏移元素选择器
}

// 返回值接口
interface DVirtualListReturn<T> {
  actualRenderData: T[];
  cacheStartIndex: number;
  size: number;
}

/**
 * 动态高度虚拟滚动钩子函数
 */
const useVirtualList = <T extends Record<string, any>>(config: DVirtualListConfig<T>): DVirtualListReturn<T> => {
  const {
    scrollContainer,
    actualHeightContainer,
    translateContainer,
    itemContainer,
    itemHeight,
    size,
    openVirtualList = true,
    defaultVirtual = true
  } = config;

  const [actualRenderData, setActualRenderData] = useState<T[]>([]);
  const [cacheStartIndex, setCacheStartIndex] = useState<number>(0);
  const [sizeRef, setSizeRef] = useState(size || 11);

  // 引用DOM元素
  const actualHeightContainerEl = useRef<HTMLElement | null>(null);
  const translateContainerEl = useRef<HTMLElement | null>(null);
  const scrollContainerEl = useRef<HTMLElement | null>(null);

  // 缓存已渲染元素的高度
  const RenderedItemsCache = useRef<{ [key: number]: number }>({});
  const dataSource = useRef<T[]>([]);

  // 当size变化时重置状态
  useEffect(() => {
    setActualRenderData([]);
    setCacheStartIndex(0);
    RenderedItemsCache.current = {};
    updateRenderData(0);
  }, [sizeRef]);

  // 处理滚动事件
  const handleScroll = useCallback((e: Event) => {
    const target = e.currentTarget as HTMLElement;
    updateRenderData(target.scrollTop);
  }, []);

  // 当数据源变化时更新
  useEffect(() => {
    dataSource.current = config.data.map(i => flattenTree(i)).flat(Infinity);
    updateRenderData(0);
  }, [config.data]);

  // 更新总高度
  const updateActualHeight = useCallback(() => {
    let actualHeight = 0;
    dataSource.current.forEach((_, index) => {
      actualHeight += getItemHeightFromCache(index);
    });
    if (actualHeightContainerEl.current) {
      actualHeightContainerEl.current!.style.height = `${actualHeight}px`;
    }
  }, []);

  // 缓存已渲染列表项的高度
  const updateRenderedItemCache = useCallback(
    (index: number) => {
      const noUpdate = Object.keys(RenderedItemsCache.current).length >= dataSource.current.length;
      if (noUpdate) return;

      // 使用requestAnimationFrame优化性能
      requestAnimationFrame(() => {
        const items = document.querySelectorAll(itemContainer);
        items.forEach((el, i) => {
          if (!RenderedItemsCache.current[index + i]) {
            RenderedItemsCache.current[index + i] = el.offsetHeight;
          }
        });
        updateActualHeight();
      });
    },
    [updateActualHeight]
  );

  // 获取缓存高度，无缓存则使用配置的itemHeight
  const getItemHeightFromCache = useCallback((index: number | string) => {
    const val = RenderedItemsCache.current[index];
    return val === undefined ? itemHeight : val;
  }, []);

  // 更新偏移值
  const updateOffset = useCallback((offset: number) => {
    if (translateContainerEl.current) {
      translateContainerEl.current.style.transform = `translateY(${offset}px)`;
    }
  }, []);

  // 更新实际渲染数据
  const updateRenderData = useCallback(
    (scrollTop: number) => {
      let startIndex = 0;
      let offsetHeight = 0;

      for (let i = 0; i < dataSource.current.length; i++) {
        offsetHeight += getItemHeightFromCache(i);
        if (offsetHeight >= scrollTop) {
          startIndex = i;
          break;
        }
      }

      setActualRenderData(dataSource.current.slice(startIndex, startIndex + sizeRef));
      setCacheStartIndex(startIndex);

      // 缓存最新列表高度项
      updateRenderedItemCache(startIndex);

      // 更新偏移值
      updateOffset(offsetHeight - getItemHeightFromCache(startIndex));
    },
    [sizeRef, getItemHeightFromCache, updateRenderedItemCache, updateOffset]
  );

  // 初始化计算
  const initCalc = useCallback(() => {
    updateRenderData(0);
  }, [updateRenderData]);

  // 组件挂载时初始化
  useEffect(() => {
    if (!defaultVirtual) {
      setActualRenderData(config.data);
      return;
    }

    if (!openVirtualList) {
      return;
    }

    let warpEl = document.documentElement;
    // 处理自定义查询ID的情况
    // if (config.queryId && config.queryId !== "dt-table" && config.queryId !== "dt-charts") {
    //   const currentClass = `.currentTable${config.queryId}`;
    //   warpEl = document.querySelector(currentClass) as HTMLElement;
    // }

    actualHeightContainerEl.current = warpEl.querySelector(actualHeightContainer);
    scrollContainerEl.current = warpEl.querySelector(scrollContainer);
    translateContainerEl.current = warpEl.querySelector(translateContainer);

    if (scrollContainerEl.current) {
      scrollContainerEl.current.addEventListener("scroll", handleScroll);
    }

    initCalc();

    // 组件卸载时清理事件
    return () => {
      if (scrollContainerEl.current) {
        scrollContainerEl.current.removeEventListener("scroll", handleScroll);
      }
    };
  }, [defaultVirtual, openVirtualList, config.queryId, handleScroll, initCalc]);

  return { actualRenderData, cacheStartIndex, size: sizeRef };
};

/**
 * 固定高度虚拟滚动钩子函数
 */
const useVirtualListFixHeight = <T extends Record<string, any>>(config: DVirtualListFixedHeightConfig<T>) => {
  const { data, itemHeight, size, actualHeightContainer, scrollContainer, translateContainer } = config;

  const [startIndex, setStartIndex] = useState(0);
  const [endIndex, setEndIndex] = useState(size - 1);

  // 引用DOM元素
  const actualHeightContainerEl = useRef<HTMLElement | null>(null);
  const translateContainerEl = useRef<HTMLElement | null>(null);
  const scrollContainerEl = useRef<HTMLElement | null>(null);

  // 计算实际渲染数据
  const actualRenderData = useMemo(() => {
    return data.slice(startIndex, endIndex + 1);
  }, [data, startIndex, endIndex]);

  // 处理滚动事件
  const handleScroll = useCallback(
    (e: Event) => {
      const target = e.currentTarget as HTMLElement;
      const { scrollTop, clientHeight, scrollHeight } = target;

      // 实际触底，且页面正常渲染全部数据时，不再触发后续计算
      if (scrollHeight <= scrollTop + clientHeight && endIndex >= data.length) {
        return;
      }

      // 保证数据渲染一直在可视区
      if (translateContainerEl.current) {
        translateContainerEl.current.style.transform = `translateY(${scrollTop}px)`;
      }

      // 渲染正确的数据
      setStartIndex(Math.floor(scrollTop / itemHeight));
      setEndIndex(startIndex + size);
    },
    [startIndex, size, itemHeight]
  );

  // 当数据源变化时更新总高度
  useEffect(() => {
    if (actualHeightContainerEl.current) {
      actualHeightContainerEl.current.style.height = `${data.length * itemHeight}px`;
    }
  }, [data, itemHeight]);

  // 组件挂载时初始化
  useEffect(() => {
    actualHeightContainerEl.current = document.querySelector(actualHeightContainer);
    scrollContainerEl.current = document.querySelector(scrollContainer);
    translateContainerEl.current = document.querySelector(translateContainer);

    if (scrollContainerEl.current) {
      scrollContainerEl.current.addEventListener("scroll", handleScroll);
    }

    // 组件卸载时清理事件
    return () => {
      if (scrollContainerEl.current) {
        scrollContainerEl.current.removeEventListener("scroll", handleScroll);
      }
    };
  }, [handleScroll]);

  return { actualRenderData };
};

export { useVirtualList, useVirtualListFixHeight };
